import * as fs from "fs";
import * as path from "path";
import * as request from "request";
import * as child_process from "child_process";

import log from "../lib/log";
import { info } from "../lib/info";
import { Config, KvData, ConsulNodeInfo } from "../typing";
import { compile, keyFormat } from "../lib/compile";
import { Service } from "../lib/service";

export default class Consul extends Service {
    /**
     * 启动consul
     */
    public async start(): Promise<Service> {
        const bin = path.resolve(this.config.service.consul.bin);
        const consulDir = this.config.service.consul.root;
        const argList = [
            "agent",
            `-bind=${ info.ip }`,
            `-node=${ info.hostname }`,
            "-config-file",
            path.resolve(consulDir, "consul.config.json"),
            `-data-dir=${ path.resolve(consulDir, "data") }`,
            `-pid-file=${ path.resolve(consulDir, "consul.pid") }`,
            `-node=${ info.hostname }`,
        ];
        log.app.info(`启动consul: ${ bin } ${ argList.join(" ") }`);
        await super.start(bin, argList);

        const kvData = Consul.compileKvData(this.config, require(this.config.service.consul.kvDateTemplate));
        await Promise.all([
            // 启动成功后清理本机残留服务
            Consul.clearService(),

            // 设置键值对
            Consul.setServerKvData(kvData),
        ]);
        return this;
    }

    public run(cmd: string, argList: string[]): child_process.ChildProcessWithoutNullStreams {
        const worker = child_process.spawn(cmd, argList);
        worker.stdout.on("data", (data: string) => {
            log[this.serviceName].info(data.toString());
        });
        worker.stderr.on("data", (data: string) => {
            log[this.serviceName].error(data.toString());
        });
        return worker;
    }

    public configService() {}

    public async healthChecks(): Promise<boolean> {
        return new Promise((resolve) => {
            log.request.info("http://localhost:8500/v1/kv/status?dc=test&token=");
            request.put({
                url: "http://localhost:8500/v1/kv/status?dc=test&token="
            }, (err, res) => {
                log.request.info("http://localhost:8500/v1/kv/status?dc=test&token=");
                resolve(!err && res.statusCode == 200 && res.body == "true");
            });
        });
    }

    /**
     * 清理未删除的服务信息
     */
    private static clearService() {
        return new Promise((resolve) => {
            log.request.info("http://localhost:8500/v1/agent/services");
            request.get({ url: "http://localhost:8500/v1/agent/services" }, (err, res, body) => {
                const service = JSON.parse(body);
                const workList = [];
                for (const id in service) {
                    workList.push(new Promise((resolve) => {
                        const url = `http://127.0.0.1:8500//v1/agent/service/deregister/${ encodeURIComponent(id) }`;
                        log.request.info(url);
                        request.get({ url }, () => {
                            log.app.info(`注销 ${ id }`);
                            resolve();
                        });
                    }));
                }
                Promise.all(workList).then(resolve);
            });
        });
    }

    /**
     * 配置各服务需要的键值对
     */
    private static setServerKvData(kvData: KvData) {
        const workList = [];
        for (const key in kvData) {
            let value = kvData[key];
            if (typeof value !== "string") {
                value = JSON.stringify(value, undefined, 4);
            }
            workList.push(new Promise(async (resolve) => {
                await Consul.setKvData(key, value);
                resolve();
            }));
        }
        return Promise.all(workList);
    }

    private static compileKvData(config: Config, kvData: KvData): KvData {
        return JSON.parse(compile(JSON.stringify(kvData), {
            ip: info.ip,
            version_tag: config.version_tag,
            name: config.version_tag.split("-").reverse()[0],
            ...keyFormat(config.service)
        }));
    }
    
    /**
     * 设置consul的键值对
     * @param {string} key 
     * @param {string} value 
     */
    private static setKvData(key: string, value: string): Promise<void> {
        return new Promise((resolve) => {
            const url = `http://localhost:8500/v1/kv/${ key }?dc=test&token=`;
            log.request.info(url);
            request.put({
                url,
                form: value.toString()
            }, (err, res) => {
                if (!err && res.statusCode == 200 && res.body == "true") {
                    log.app.info(`设置成功: { ${ key }: ${ value } }`);
                } else {
                    log.app.info(`设置失败: { ${ key }: ${ value } }`);
                }
                resolve();
            });
        });
    }
    
    /**
     * 更新键值对
     * @param {object} kvData 新键值对
     * @param {object} source 旧键值对
     */
    public static updataKv(config: Config, kvData: KvData, source: KvData) {
        source = Consul.compileKvData(config, source);
        for (const key in source) {
            let newValue = source[key];
            if (typeof newValue === "object") {
                newValue = JSON.stringify(newValue, undefined, 4);
            }
            let oldValue = kvData[key];
            if (typeof oldValue === "object") {
                oldValue = JSON.stringify(oldValue, undefined, 4);
            }
            if (newValue !== oldValue) {
                kvData[key] = newValue;
                Consul.setKvData(key, newValue);
            }
        }
    }

    /**
     * 获取服务对应的所有节点
     * @param serviceName 服务名称
     */
    public static getServiceNode(serviceName: string): Promise<ConsulNodeInfo[]> {
        return new Promise((resolve, reject) => {
            request.get(`http://127.0.0.1:8500/v1/health/service/${ serviceName }`, { json: true }, (err, res) => {
                if (!err && res.statusCode == 200 && res.body) {
                    resolve(res.body);
                } else {
                    reject(err);
                }
            });
        });
    }

    /**
     * 获取服务名节点已启动的所有tag
     * @param serviceName 服务名
     */
    public static async getServicrTags(serviceName: string): Promise<string[]> {
        try {
            const tagSet = new Set<string>();
            const nodeList = await Consul.getServiceNode(serviceName);
            for (const node of nodeList) {
                if (node.Service && Array.isArray(node.Service.Tags)) {
                    node.Service.Tags.forEach((tag) => tagSet.add(tag));
                }
            }
            return [...tagSet];
        } catch (error) {
            return [];
        }
    }

    public static updateConfig(config: Config, configStr: string): Promise<boolean> {
        return new Promise((resolve) => {
            const template = config.service["consul"].kvDateTemplate;
            fs.writeFile(template, configStr, (error) => {
                if (error) {
                    log["consul"].info("updateConfig", configStr, error);
                    resolve(false);
                } else {
                    resolve(true);
                }
            });
        });
    }

    public static getConfig(config): Promise<string> {
        const template = config.service["consul"].kvDateTemplate;
        return new Promise((resolve) => {
            fs.readFile(template, (error, data) => {
                if (error) {
                    log["consul"].info("getConfig", error);
                    resolve("");
                } else {
                    resolve(data.toString());
                }
            });
        });
    }
}

